package com.android.settings.audio.peq.qr;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.widget.ListView;
import android.widget.TextView;

import com.android.settings.audio.peq.peq.ui.dialog.CommanDialog;
import com.android.settings.R;
import com.android.settings.audio.peq.peq.ui.adapter.DialogAdapter;
import com.android.settings.audio.peq.qr.annotation.QrImpl;
import com.android.settings.audio.peq.qr.decoder.EqV1Decoder;
import com.android.settings.audio.peq.qr.decoder.MsebV1Decoder;
import com.android.settings.audio.peq.qr.decoder.PeqV1Decoder;
import com.android.settings.audio.peq.qr.encoder.EqV1Encoder;
import com.android.settings.audio.peq.qr.encoder.MsebV1Encoder;
import com.android.settings.audio.peq.qr.encoder.PeqV1Encoder;
import com.android.settings.audio.peq.qr.util.ClassesReader;
import com.android.settings.audio.peq.util.BuildConfig;
import com.android.settings.audio.peq.util.LogPlus;
import com.android.settings.audio.peq.xqrcode.XQRCode;
import com.android.settings.audio.peq.xqrcode.util.QRCodeAnalyzeUtils;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by We on 2023/5/23
 */
public class QrManager {
    private final Map<String, AbsDecoder> decoderMap;
    private final Map<String, AbsEncoder> encoderMap;

    private QrManager() {
        decoderMap = new HashMap<>();
        encoderMap = new HashMap<>();
    }

    private static final class InstanceHolder {
        private static final QrManager instance = new QrManager();
    }

    public static QrManager getInstance() {
        return InstanceHolder.instance;
    }

    public void init(Context context) {
        //todo 自动注册已有的 Decoder,Encoder
        String scanPkg = context.getPackageName() + ".audio.peq.qr";
        //scanPackage(scanPkg);
        scanPackage(scanPkg, context);

        ////////扫描包名路径可能错误导致无法找到encoder,decoder////////
        if (encoderMap.isEmpty()) {
            regEncoder(new PeqV1Encoder());
            regEncoder(new EqV1Encoder());
            regEncoder(new MsebV1Encoder());
        }
        if (decoderMap.isEmpty()) {
            regDecoder(new PeqV1Decoder());
            regDecoder(new EqV1Decoder());
            regDecoder(new MsebV1Decoder());
        }
        ////////扫描包名路径可能错误导致无法找到encoder,decoder////////
    }

    private void scanPackage(String pkg) {
        String pkgDir = pkg.replace(".", "/");
        if (BuildConfig.DEBUG) LogPlus.d("###pkgDir:" + pkgDir);
        java.net.URL url = Thread.currentThread().getContextClassLoader().getResource(pkgDir);
        if (url == null) {
            LogPlus.d("###url is null###");
            return;
        }
        File file = new File(url.getFile());
        File[] fs = file.listFiles(f -> {
            String filename = file.getName();
            if (f.isDirectory()) {
                scanPackage(pkg + "." + filename);
                return false;
            }
            return filename.endsWith(".class");
        });
        if (fs == null) return;
        for (File f : fs) {
            String name = f.getName();
            //去掉.class
            name = name.substring(0, name.lastIndexOf("."));
            String pkgCls = pkg + "." + name;
            try {
                scanCoders(pkgCls);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void scanPackage(String pkg, Context context) {
        String pkgDir = pkg.replace(".", "/");
        if (BuildConfig.DEBUG) LogPlus.d("###pkgDir:" + pkgDir);
        List<Class<?>> classes = ClassesReader.read(pkg, context);
        for (Class<?> c : classes) {
            try {
                if (BuildConfig.DEBUG) LogPlus.d("###class: " + c.getName());
                scanCoders(c);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void scanCoders(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class<?> c = Class.forName(className);
        scanCoders(c);
    }

    private void scanCoders(Class<?> clazz) throws IllegalAccessException, InstantiationException {
        if (BuildConfig.DEBUG) LogPlus.d("###scan class:" + clazz.getSimpleName());
        if (clazz.isAnnotationPresent(QrImpl.class)) {
            QrImpl qrImpl = clazz.getAnnotation(QrImpl.class);
            if (qrImpl == null) return;
            String id = qrImpl.id();
            if (BuildConfig.DEBUG) LogPlus.d("###qrImpl id:" + id);
            Object coder = clazz.newInstance();
            if (coder instanceof AbsDecoder) {
                ((AbsDecoder) coder).id = id;
                regDecoder((AbsDecoder) coder);
            } else if (coder instanceof AbsEncoder) {
                ((AbsEncoder) coder).id = id;
                regEncoder((AbsEncoder) coder);
            }
        }
    }

    public void startScan(Activity activity, int requestCode) {
        XQRCode.startScan(activity, requestCode);
    }

    public void decodeQrcode(String imagePath, OnDecodeCallback onDecodeCallback) {
        XQRCode.analyzeQRCode(imagePath, new QRCodeAnalyzeUtils.AnalyzeCallback() {
            @Override
            public void onAnalyzeSuccess(Bitmap bitmap, String result) {
                if (onDecodeCallback != null) onDecodeCallback.onDecoded(imagePath, result);
            }

            @Override
            public void onAnalyzeFailed() {
                if (onDecodeCallback != null) onDecodeCallback.onDecodeFailed(imagePath, new Exception("onAnalyzeFailed"));
            }
        });
    }

    /**
     * 令已注册的解码intent处理二维码内容
     *
     * @param qrContent 二维码内容
     */
    public Object decodeQrcodeByDecoder(String qrContent) throws Exception {
        AbsDecoder decoder = detectDecoder(qrContent);
        if (decoder != null) {
            return decoder.onDecoded(qrContent);
        }
        throw new BusinessException("decoder not found");
    }

    public Bitmap createQrcodeByEncoder(String id, byte[] data, int width, int height, Bitmap logo) throws BusinessException {
        for (Map.Entry<String, AbsEncoder> entry : encoderMap.entrySet()) {
            if (entry.getValue().getId().equals(id)) {
                String qrContent = entry.getValue().generateContent(data);
                return createQrcode(qrContent, width, height, logo);
            }
        }
        throw new BusinessException("encoder not found, id:" + id);
    }

    public AbsDecoder detectDecoder(String qrContent) throws Exception {
        for (Map.Entry<String, AbsDecoder> entry : decoderMap.entrySet()) {
            if (entry.getValue().canDecode(qrContent)) {
                return entry.getValue();
            }
        }
        throw new BusinessException("decoder not found");
    }

    public void showImportSourceDialog(final List<String> itemsList, Context context, OnSelectedCallback importSourceSelectedCallback) {
        final CommanDialog dialog = new CommanDialog(context, R.style.peqPopDialogStyle, CommanDialog.STYLE_POP_NOBUTTON);
        dialog.setCanceledOnTouchOutside(true);
        dialog.addBottomView(R.layout.peq_dialog_listview_3);
        dialog.setCanceledOnTouchOutside(true);

        ListView listView = dialog.getContentView().findViewById(R.id.dialog_listview);
        TextView titleTextView = dialog.titleTextView;
        titleTextView.setText(R.string.peq_import2);
        DialogAdapter dialogAdapter = new DialogAdapter(context, itemsList, false, true);
        listView.setAdapter(dialogAdapter);
        listView.setOnItemClickListener((arg0, arg1, arg2, arg3) -> {
            String clickItemString = itemsList.get(arg2);
            if (importSourceSelectedCallback != null) importSourceSelectedCallback.onSelected(clickItemString);
            dialog.dismiss();
        });
        dialog.show();
    }

    public void showExportDesDialog(final List<String> itemsList, Context context, OnSelectedCallback onSelectedCallback) {
        final CommanDialog dialog = new CommanDialog(context, R.style.peqPopDialogStyle, CommanDialog.STYLE_POP_NOBUTTON);
        dialog.setCanceledOnTouchOutside(true);
        dialog.addBottomView(R.layout.peq_dialog_listview_3);
        dialog.setCanceledOnTouchOutside(true);

        ListView listView = dialog.getContentView().findViewById(R.id.dialog_listview);
        TextView titleTextView = dialog.titleTextView;
        titleTextView.setText(R.string.peq_export);

        DialogAdapter dialogAdapter = new DialogAdapter(context, itemsList, false, true);
        listView.setAdapter(dialogAdapter);
        listView.setOnItemClickListener((arg0, arg1, arg2, arg3) -> {
            String clickItemString = itemsList.get(arg2);
            if (onSelectedCallback != null) onSelectedCallback.onSelected(clickItemString);
            dialog.dismiss();
        });
        dialog.show();
    }

    public interface OnSelectedCallback {
        void onSelected(String clickItemString);
    }

    public void setOnDecodeCallback(OnDecodeCallback onDecodeCallback) {
        XQRCode.setOnDecodeCallback(new XQRCode.OnDecodeCallback() {
            @Override
            public void onDecoded(String imagePath, String qrContent) {
                if (null != onDecodeCallback) onDecodeCallback.onDecoded(imagePath, qrContent);
            }

            @Override
            public void onDecodeFailed(String imagePath, Exception e) {
                if (null != onDecodeCallback) onDecodeCallback.onDecodeFailed(imagePath, e);
            }
        });
    }

    public interface OnDecodeCallback {
        void onDecoded(String imagePath, String qrContent);

        void onDecodeFailed(String imagePath, Exception e);
    }

    public Bitmap createQrcode(String content, int width, int height, Bitmap logo) {
        Bitmap qrBitmap = XQRCode.createQRCodeWithLogo(content, width, height, logo);
        return qrBitmap;
    }

    public void regDecoder(AbsDecoder decodeIntent) {
        this.decoderMap.put(decodeIntent.getId(), decodeIntent);
    }

    public void regEncoder(AbsEncoder encoder) {
        this.encoderMap.put(encoder.getId(), encoder);
    }

    public AbsEncoder getEncoder(String id) {
        return encoderMap.get(id);
    }

    public AbsDecoder getDecoder(String id) {
        return decoderMap.get(id);
    }

    public static abstract class AbsDecoder {
        protected String id;
        protected String from;

        public abstract boolean canDecode(String content);

        public abstract Object onDecoded(String content) throws Exception;

        @Override
        public boolean equals(Object obj) {
            if (obj == null) return false;
            if (obj == this) return true;
            if (!(obj instanceof AbsDecoder)) {
                return false;
            }
            return ((AbsDecoder) obj).id.equals(id);
        }

        public String getId() {
            assertIdNotNull();
            return id;
        }

        private void assertIdNotNull() {
            if (id == null) throw new RuntimeException("id is null");
        }

        @Override
        public int hashCode() {
            assertIdNotNull();
            return id.hashCode();
        }

        @Override
        public String toString() {
            return "Decoder{" +
                    "id='" + id + '\'' +
                    ", from='" + from + '\'' +
                    '}';
        }
    }

    public static abstract class AbsEncoder {
        protected String id;
        protected String from;

        public abstract String generateContent(byte[] data);

        public abstract byte[] toBytes(Object dataEntity) throws Exception;

        @Override
        public boolean equals(Object obj) {
            if (obj == null) return false;
            if (obj == this) return true;
            if (!(obj instanceof AbsEncoder)) {
                return false;
            }
            return ((AbsEncoder) obj).id.equals(id);
        }

        public String getId() {
            assertIdNotNull();
            return id;
        }

        private void assertIdNotNull() {
            if (id == null) throw new RuntimeException("id is null");
        }

        @Override
        public int hashCode() {
            assertIdNotNull();
            return id.hashCode();
        }

        @Override
        public String toString() {
            return "Encoder{" +
                    "id='" + id + '\'' +
                    ", from='" + from + '\'' +
                    '}';
        }
    }

    public static class BusinessException extends RuntimeException {
        public BusinessException(String msg) {
            super(msg);
        }
    }
}
